package com.gitee.zycra.union.api.common.util;

import com.gitee.zycra.union.api.common.exceptions.IdGeneratorException;
import com.gitee.zycra.union.api.common.model.MachineId;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * @author zycra
 */
@Component
public final class IdGenerator {

    /**
     * 起始的时间戳 2022-01-01 00:00:00
     */
    private static final long START_STAMP = 1640966400000L;

    /**
     * 序列号位数
     */
    private static final long SEQUENCE_BIT = 12;

    /**
     * 机器标识位数
     */
    private static final long MACHINE_BIT = 10;

    /**
     * 每一部分的最大值
     */
    private static final long MAX_MACHINE_NUM = ~(-1L << MACHINE_BIT);
    private static final long MAX_SEQUENCE = ~(-1L << SEQUENCE_BIT);

    /**
     * 每一部分向左的位移
     */
    private static final long MACHINE_LEFT = SEQUENCE_BIT;
    private static final long TIMESTAMP_LEFT = MACHINE_LEFT + MACHINE_BIT;

    /**
     * 机器号
     */
    private long machineId;

    /**
     * 序号
     */
    private static long sequence = 0L;

    /**
     * 上次时间戳
     */
    private static long lastStamp = -1L;

    @Autowired(required = false)
    public void setMachineId(MachineId machineId) {
        if (machineId == null) {
            return;
        }
        Integer id = machineId.getId();
        if (id > MAX_MACHINE_NUM || id < 0) {
            throw new IllegalArgumentException("machineId can't be greater than MAX_MACHINE_NUM or less than 0");
        }
        this.machineId = id;
    }

    /**
     * 生成ID
     *
     * @return id
     */
    public synchronized long nextId() {
        long currStamp = System.currentTimeMillis();
        if (currStamp < lastStamp) {
            throw new IdGeneratorException("Clock moved backwards, Refuse to generate id");
        }
        if (currStamp == lastStamp) {
            sequence = (sequence + 1) & MAX_SEQUENCE;
            if (sequence == 0L) {
                currStamp = System.currentTimeMillis();
                while (currStamp <= lastStamp) {
                    currStamp = System.currentTimeMillis();
                }
            }
        } else {
            sequence = 0L;
        }
        lastStamp = currStamp;
        return (currStamp - START_STAMP) << TIMESTAMP_LEFT
                | machineId << MACHINE_LEFT
                | sequence;
    }

}
